library IEEE;
Library UNISIM;
use IEEE.STD_LOGIC_1164.ALL;
-- use IEEE.std_logic_signed.ALL;
use IEEE.NUMERIC_STD.ALL;
use UNISIM.vcomponents.all;
 
--- Decode unit, takes in the data in IFID register and decode the instruction

-- |6 bits| ALU OPcode [EX]
-- |1 bit| ALU source [EX]
-- |1 bit| register destination [EX]	

-- |1 bit| branch signal [DM]
-- |1 bit| memory read signal [DM]
-- |1 bit| memory write signal [DM]

-- |1 bit| memory to register signal [WB]
-- |1 bit| register write [WB]

-- |32 bits| next pc
-- |32 bits| output data 1
-- |32 bits| output data 2
-- |32 bits| i-type offset, sign extension to 32 bits
-- |5 bits| destination 1
-- |5 bits| destination 2

entity DECODE_UNIT is
	PORT (
		clk : in STD_LOGIC;

		next_pc_in : in STD_LOGIC_VECTOR(31 downto 0);
		next_instruction : in STD_LOGIC_VECTOR(31 downto 0);
		
		wb_write_dst : in STD_LOGIC_VECTOR(4 downto 0);
		wb_write_data : in STD_LOGIC_VECTOR(31 downto 0);
		wb_write_en : in STD_LOGIC;

		-- For data hazards!
		mem_read_feedback : in STD_LOGIC; -- For data hazards. To check if there are data hazards
		rt_index_feedback : in STD_LOGIC_VECTOR(4 downto 0); -- To check if register for lw is equal to any one we are currently using
		--

		-- For branch hazards (forwarding)
		ex_mem_is_reg_write : in STD_LOGIC; -- Is there something being written to a register, that is currently in the ex/mem register
		ex_mem_reg_write_index : in STD_LOGIC_VECTOR(4 downto 0); -- ^ What register index is this?
		ex_mem_reg_data : in STD_LOGIC_VECTOR(31 downto 0);
		--
	
		-- For branch hazards (stall) (caused by ...)
		id_ex_is_reg_write : in STD_LOGIC; -- Is there something being written to a register, that is currently in the id/ex register
		id_ex_reg_write_index : in STD_LOGIC_VECTOR(4 downto 0); -- ^ What register index is this?
		--

		-- For branch hazards (stall) (caused by...)
		ex_mem_is_mem_read : in STD_LOGIC;
		ex_mem_mem_to_reg : in STD_LOGIC;

		invalid : out STD_LOGIC; -- assert '1' when the instruction is undefined

		ALU_op : out STD_LOGIC_VECTOR(5 downto 0);
		ALU_src : out STD_LOGIC;
		reg_dst : out STD_LOGIC; -- 0 = rt, 1 = rd

		mem_read : out STD_LOGIC;
		mem_write : out STD_LOGIC;

		mem_reg : out STD_LOGIC;
		reg_write_en : out STD_LOGIC;

		next_pc_out : out STD_LOGIC_VECTOR(31 downto 0);
		data_outp1 : out STD_LOGIC_VECTOR(31 downto 0);
		data_outp2 : out STD_LOGIC_VECTOR(31 downto 0);
		immediate_offset : out STD_LOGIC_VECTOR(31 downto 0);
		wb_dst1 : out STD_LOGIC_VECTOR(4 downto 0);
		wb_dst2 : out STD_LOGIC_VECTOR(4 downto 0);

		-- Shifting branch handling into ID
		is_branch_out : out STD_LOGIC;
		branch_to_here : out STD_LOGIC_VECTOR(31 downto 0);

		-- Jump handling in ID
		is_jump_out : out STD_LOGIC;
		jump_to_here : out STD_LOGIC_VECTOR(31 downto 0);

		-- For read before write hazards
		-- Register indices that were fed from DEC to EX layers
		rs_index : out STD_LOGIC_VECTOR(4 downto 0); 
		rt_index : out STD_LOGIC_VECTOR(4 downto 0); -- Typically already in wb_dst2; the synthesizer should realize this
		-- *** RT index also used for Memory-Memory fixes via forwarding from DM-WB to DM
				
		-- For data hazards
		if_stall : out STD_LOGIC;
		pc_stall : out STD_LOGIC;
		
		-- For Branch hazards
		if_flush : out STD_LOGIC -- Controls whether in the next cylce, IF flushes or not. NOT a registered output!
		
	); 
end DECODE_UNIT;

architecture Behavioral of DECODE_UNIT is
	
	component registers is
	Port (
		clk : in STD_LOGIC;
		reg1_read_addr : in STD_LOGIC_VECTOR(4 downto 0);
		reg2_read_addr : in STD_LOGIC_VECTOR(4 downto 0);

		reg_write_en : in STD_LOGIC;

		reg_write_addr : in STD_LOGIC_VECTOR(4 downto 0);
		data_inp : in STD_LOGIC_VECTOR(31 downto 0);

		data_outp1 : out STD_LOGIC_VECTOR(31 downto 0);
		data_outp2 : out STD_LOGIC_VECTOR(31 downto 0)
	);
	end component;
	
	component branch_hazard_unit is
	PORT(
		
	ex_mem_is_reg_write : in STD_LOGIC;
	ex_mem_reg_write_index : in STD_LOGIC_VECTOR(4 downto 0);
	reg1_read_addr : in STD_LOGIC_VECTOR(4 downto 0);
	reg2_read_addr : in STD_LOGIC_VECTOR(4 downto 0);
	ex_mem_reg_data : in STD_LOGIC_VECTOR(31 downto 0);
	
	id_ex_is_reg_write : in STD_LOGIC;
	id_ex_reg_write_index : in STD_LOGIC_VECTOR(4 downto 0);
	ex_mem_is_mem_read : in STD_LOGIC;
	ex_mem_mem_to_reg : in STD_LOGIC;
	
	reg_data_out1 : in STD_LOGIC_VECTOR(31 downto 0);
	reg_data_out2 : in STD_LOGIC_VECTOR(31 downto 0);
	
	-- 00 for non-branch, 01 for beq, 10 for bgez and bgezal
	comparison_type : in STD_LOGIC_VECTOR(1 downto 0);
	
	is_branch : out STD_LOGIC;
	is_branch_stall : out STD_LOGIC
	
	);

	end component;

	signal reg1_read_addr : STD_LOGIC_VECTOR(4 downto 0);
	signal reg2_read_addr : STD_LOGIC_VECTOR(4 downto 0);

	signal reg_data_out1 : STD_LOGIC_VECTOR(31 downto 0);
	signal reg_data_out2 : STD_LOGIC_VECTOR(31 downto 0);

	signal branch_comparison_type : STD_LOGIC_VECTOR(1 downto 0);

	signal is_data_hazard_stall : STD_LOGIC;
	-- signal is_branch_successful_comp : STD_LOGIC; -- '1' if there was a successful comparison check for BRANCH. Does not take into account the posibility of required stalls!
	signal is_branch : STD_LOGIC;  -- Whether there was an actual branch taken (no potential hazards)
	signal is_branch_stall : STD_LOGIC;
	signal is_jump : STD_LOGIC;
	
	signal countdown : STD_LOGIC_VECTOR(4 downto 0) := "00000"; -- maximum stall of 32 cycles
	signal countdown_done : STD_LOGIC;
	signal is_arith_stall : STD_LOGIC;
	signal not_countdown : STD_LOGIC;
	------------------------------
	-- Helper signals
	------------------------------
	signal opcode : STD_LOGIC_VECTOR(5 downto 0);
	signal funct : STD_LOGIC_VECTOR(5 downto 0);
	signal is_lui : STD_LOGIC;
	signal sign_extended : STD_LOGIC_VECTOR(31 downto 0);
	signal no_sign_extend : STD_LOGIC;
	signal is_shift_immediate : STD_LOGIC;
	signal is_shift_variable : STD_LOGIC;
	signal is_link : STD_LOGIC;
	signal is_jump_to_reg : STD_LOGIC;
	signal link_register_index : STD_LOGIC_VECTOR(4 downto 0);

begin
	
	register_guy : registers
	PORT MAP(
		clk => clk,

		reg1_read_addr => reg1_read_addr,
		reg2_read_addr => reg2_read_addr,

		reg_write_en => wb_write_en,

		reg_write_addr => wb_write_dst,
		data_inp => wb_write_data,

		data_outp1 => reg_data_out1,
		data_outp2 => reg_data_out2
	);
	
	bhu : branch_hazard_unit
	PORT MAP(
		ex_mem_is_reg_write => ex_mem_is_reg_write,
		ex_mem_reg_write_index =>ex_mem_reg_write_index ,
		reg1_read_addr => reg1_read_addr,
		reg2_read_addr => reg2_read_addr,
		ex_mem_reg_data => ex_mem_reg_data,
		
		id_ex_is_reg_write =>id_ex_is_reg_write,
		id_ex_reg_write_index => id_ex_reg_write_index,
		ex_mem_is_mem_read => ex_mem_is_mem_read,
		ex_mem_mem_to_reg => ex_mem_mem_to_reg ,
		
		reg_data_out1 => reg_data_out1,
		reg_data_out2 => reg_data_out2,
		
		-- 00 for non-branch, 01 for beq, 10 for bgez and bgezal
		comparison_type => branch_comparison_type,
		
		is_branch => is_branch,
		is_branch_stall => is_branch_stall
	
	);
	
	branch_comparison_type <= "01" when opcode = "000100" 
	else "10" when opcode = "000001" 
	else "11" when is_jump_to_reg = '1' 
	else "00";
	
	is_jump <= '1' when opcode = "000010" or opcode = "000011" else '0';
	
	-- Special case where this is a JR type instruction... not sure why this is considered R-TYPE also
	is_jump_to_reg <= '1' when (opcode = "000000" and funct = "001000") 
	or (opcode = "000000" and funct = "001001")
	else '0';
	
	-- Signals to be fed into register file --
	-- Register read addresses
	reg1_read_addr <= next_instruction(25 downto 21);
	reg2_read_addr <= next_instruction(20 downto 16);

	-- Helper signals	
	opcode <= next_instruction(31 downto 26);
	funct <= next_instruction(5 downto 0);
	is_lui <= '1' when opcode = "001111" else '0';
	
	-- Offset to be extended before being fed to ALU, eg, for BRANCH
	sign_extended <= (31 downto 16 => next_instruction(15)) & next_instruction(15 downto 0);
	
	-----------------------------------------------------------------------------
	-- Data Stall Check
	-- Conditions : 
	-- 1) R-Type instruction
	-- 2) Either RS or RT have the same index as the destination reg write index
				
	-- OR
				
	-- 1) Some I-type instructions, BNE, LW etc.
	-- 2) 
				
	-- *** Note that select I type operations ALSO need to be addressed, but in different ways ***
	-- *** Some i-type operations do not require stalling (eg. lui)
	-----------------------------------------------------------------------------
	is_data_hazard_stall <= '1' when 
			((opcode = "000000") -- Only for R-Type instructions
			and mem_read_feedback = '1' -- previous instruction was LW
			and (rt_index_feedback = next_instruction(25 downto 21) or rt_index_feedback = next_instruction(20 downto 16)))
		or ((opcode = "100011" or opcode = "001101" or opcode = "001000" or opcode = "001010")-- Load word or ORI or ADDI or SLTI>
			and mem_read_feedback = '1' -- previous instruction was LW
			and (rt_index_feedback = next_instruction(25 downto 21))) -- only RS matters, RT being modified makes no diff
		else '0';
	
	--- ^ Also take into account other i_type instructions, and j instructions

	--- Address of which to branch to
	branch_to_here <= STD_LOGIC_VECTOR(unsigned(sign_extended(29 downto 0) & "00") + unsigned(next_pc_in)) when is_jump_to_reg = '0'
											else ex_mem_reg_data when ex_mem_is_reg_write = '1'  and ex_mem_reg_write_index = reg1_read_addr
											else reg_data_out1;	
											-- ^ Address we wish to jump to for JAR related stuff. REMEMBER TO FORWARD!

	--- Address of which to jump to (is it to a register, or immediate?)
	jump_to_here <= STD_LOGIC_VECTOR(next_pc_in(31 downto 28) & next_instruction(25 downto 0) & "00") ;
	
	is_jump_out <= is_jump;

	is_branch_out <= is_branch; -- branch instructions cannot be jump instructions and vice versa, its a little confusing ,but allows us to reuse hardware for both jump and branch hazards...
	
	IF_FLUSH <= is_branch or is_jump;
	
	-- Whether this is an instruction which requires a link
	is_link <= '1' when (opcode = "000001" and next_instruction(20 downto 16) = "10001") -- bgezal
	or (opcode = "000011") -- jal
	or (opcode = "000000" and funct = "001001") -- jral
	else '0';
	
	-- Where are are linking to
	link_register_index <= next_instruction(15 downto 11) when (opcode = "000000" and funct = "001001") else "11111";
	
	-- For mult/div instructions, where stalls are required
	-- is_arith_stall <= '1' when (opcode = "000000" and (funct = "011000" or funct = "011001" or funct = "011011" or funct = "011010")) else '0';
	is_arith_stall <= '1' when (countdown /= "00000" and (opcode = "000000" and (funct = "010000" or funct = "010010"))) else '0';
	
	no_sign_extend <= '1' when (opcode = "001101") else '0'; -- ORI
	
	is_shift_immediate <= '1' when (opcode = "000000" and (funct = "000000" or funct = "000010" or funct = "000011")) else '0'; -- SLL and SRL, SRA
	is_shift_variable <= '1' when (opcode = "000000" and (funct = "000100" or funct = "000110" or funct = "000111")) else '0';
	
	--- Inform PC and IF/ID to freeze their values if required (data hazard)
	if_stall <= is_data_hazard_stall or is_branch_stall or (is_arith_stall);
	pc_stall <= is_data_hazard_stall or is_branch_stall or (is_arith_stall);
		
	process (clk)
		variable invalid_inc : STD_LOGIC;
	begin
	
		if clk'event and clk = '1' then
			
			if (countdown /= "00000") then
				countdown <= std_logic_vector(unsigned(countdown) - 1);
			end if;
			
			if (is_arith_stall = '1') then
						
				-- We have some counting down to do
				-- countdown <= std_logic_vector(unsigned(countdown) - 1);
				-- NEED TO UPDATE the register values, because the old input to the ALU might have been
				-- previously a forwarded one!
				-- data_outp1 <= reg_data_out1;
				-- data_outp2 <= reg_data_out2;

				ALU_src <= '0'; 
						reg_dst <= '0'; 
						mem_read <= '0';
						mem_write <= '0';
						mem_reg <= '0';
						reg_write_en <= '0';
						ALU_op <= "000000";
						rs_index <= "00000";
						rt_index <= "00000";
						immediate_offset <= (31 downto 0 => '0');
						wb_dst1 <= "00000";
						wb_dst2 <= "00000";
						data_outp1 <= reg_data_out1;
						data_outp2 <= reg_data_out2;
				
			else
				-- Assign some temporary helper variables
				invalid_inc := '0'; -- By default, the instruction is valid.
				
				if (opcode = "000100" or opcode = "000010" or opcode = "000001" or (is_jump_to_reg = '1') or opcode = "000011") then -- branch equal, jump, bgez etc.
					--- If a link is not required, A NOP IS GOING TO BE PUT INTO the ID/EX buffer!
					--- This happens regardless of stall, flush, or "fall through"
					--- If this is a linking branch/jump, then we will ouput nops only if it is NOT truely time to branch
					if (is_link = '0') then
						ALU_src <= '0'; 
						reg_dst <= '0'; 
						mem_read <= '0';
						mem_write <= '0';
						mem_reg <= '0';
						reg_write_en <= '0';
						ALU_op <= "000000";
						rs_index <= "00000";
						rt_index <= "00000";
						immediate_offset <= (31 downto 0 => '0');
						wb_dst1 <= "00000";
						wb_dst2 <= "00000";
						data_outp1 <= reg_data_out1;
						data_outp2 <= reg_data_out2;
					else
						-- This is a linking instruction, and it is time to let go of the stall!
						ALU_src <= '1';  -- Use immediate
						reg_dst <= '1'; 
						mem_read <= '0';
						mem_write <= '0';
						mem_reg <= '0';
						reg_write_en <= '1';
						ALU_op <= "001001"; -- We add with an immediate
						rs_index <= "00000"; -- not required, used for forwarding to this instruction
						rt_index <= "00000"; -- not required, used for forwarding to this instruction
						immediate_offset <= next_pc_in; -- Store the new address
						wb_dst1 <= "00000";
						wb_dst2 <= link_register_index;
						data_outp1 <= x"00000000";
						data_outp2 <= reg_data_out2;
					end if;
				elsif (is_data_hazard_stall = '1') then			
					--- Stall the pipeline!
					--- Make ID/EX to NOP (the manually inserted "NOP")
					ALU_src <= '0'; 
					reg_dst <= '0'; 
					mem_read <= '0';
					mem_write <= '0';
					mem_reg <= '0';
					reg_write_en <= '0';
					ALU_op <= "000000";
					rs_index <= "00000";
					rt_index <= "00000";
					immediate_offset <= (31 downto 0 => '0');
					wb_dst1 <= "00000";
					wb_dst2 <= "00000";
					data_outp1 <= reg_data_out1;
					data_outp2 <= reg_data_out2;
				else 
					-----------------------------------------------------------------------------
					-- No Data stall, no Jump stall, go ahead normally
					-----------------------------------------------------------------------------
					
					-- Register the register values
					if (is_shift_variable = '1') then
						-- We need to swap the two variables for variable shifts - this is part of our ALU requirements
						data_outp1 <= reg_data_out2;
						data_outp2 <= reg_data_out1;
					else
						if (is_lui = '1') then
							data_outp1 <= x"00000000"; -- For LUI, data_outp1 is r0.
						elsif (is_shift_immediate = '1') then
							data_outp1 <= reg_data_out2; -- rt is the source operand for these operations, so we swap this around
						else 
							data_outp1 <= reg_data_out1;
						end if;
						data_outp2 <= reg_data_out2;
					end if;
					
					-- Next PC values
					next_pc_out <= next_pc_in;
					
					--- Choose immediate value
					--- LUI has a special treatment in terms of offset.
					if (is_lui = '1') then
						immediate_offset <= (next_instruction(15 downto 0) & x"0000"); -- shift left by 16
					elsif (is_shift_immediate = '1') then
						immediate_offset <= "000000000000000000000000000" & next_instruction(10 downto 6);
					elsif (no_sign_extend = '1') then
						immediate_offset <= x"0000" & next_instruction(15 downto 0);
					else
						immediate_offset <= sign_extended;
					end if;
					
					--- Possible destinations for register writeback
					wb_dst1 <= next_instruction(20 downto 16); -- rt
					wb_dst2 <= next_instruction(15 downto 11); -- rd
				
					if (is_shift_immediate = '1') then
						-- instruction is an immediate shift, need to "put rt's index into rs's location"
						rs_index <= next_instruction(20 downto 16); -- (RT) - this is not a bug! This is intended!
						rt_index <= "00000";
					elsif (is_shift_variable = '1') then
						-- For shifts, due to our ALU design, the number to be shifted is in (25 downto 21).
						-- But for MIPs, it is the other way round - hence our forwarding must be done in the opposite fashion
						-- THIS IS NOT A BUG!
						rs_index <= next_instruction(20 downto 16);
						rt_index <= next_instruction(25 downto 21);
					else
						--- Register indices for rs and rt, used to eliminate data hazards
						rs_index <= next_instruction(25 downto 21); -- RS
						rt_index <= next_instruction(20 downto 16); -- RT
					end if;
					
					----------------------------- HANDLE CONTROL SIGNALS BASED ON OPCODE AND FUNCT -------------------------------
					-- R type
					if (opcode = "000000") then	
						
						if (is_shift_immediate = '1') then
							ALU_src <= '1'; -- Choose immediate
							reg_dst <= '1'; -- choose rd as destination
							
							mem_read <= '0';
							mem_write <= '0';
							
							mem_reg <= '0';
							reg_write_en <= '1';
						else
							ALU_src <= '0'; -- choose rt
							reg_dst <= '1'; -- choose rd
				
							mem_read <= '0';
							mem_write <= '0';
				
							mem_reg <= '0';
							reg_write_en <= '1';
						end if;
						
						-- ADDU
						if (funct = "100001") then
							ALU_op <= "001000";
						-- SUBU
						elsif (funct = "100011") then
							ALU_op <= "001100";
						-- AND
						elsif (funct = "100100") then
							ALU_op <= "000001";
						-- OR
						elsif (funct = "100101") then
							ALU_op <= "000010";
						-- NOR
						elsif (funct = "100111") then
							ALU_op <= "000011";
						-- XOR
						elsif (funct = "100110") then
							ALU_op <= "000100";
						-- SLT
						elsif (funct = "101010") then
							ALU_op <= "001111";
						-- SLLV
						elsif (funct = "000100") then
							ALU_op <= "010010";
						-- SRLV
						elsif (funct = "000110") then
							ALU_OP <= "010110";
						-- SRAV
						elsif (funct = "000111") then
							ALU_OP <= "010100";
						-- MULT
						elsif (funct = "011000") then
							ALU_op <= "011001";
							Countdown <= STD_LOGIC_VECTOR(to_unsigned(4, countdown'length));
						-- MULTU
						elsif (funct = "011001") then
							ALU_op <= "011000";
							Countdown <= STD_LOGIC_VECTOR(to_unsigned(4, countdown'length));
						-- DIVU
						elsif (funct = "011011") then
							ALU_op <= "011100";
							Countdown <= STD_LOGIC_VECTOR(to_unsigned(17, countdown'length));
						-- DIV
						elsif (funct = "011010") then
							ALU_OP <= "011101";
							Countdown <= STD_LOGIC_VECTOR(to_unsigned(17, countdown'length));
						-- MFHI
						elsif (funct = "010000") then
							ALU_op <= "011111";
						-- MFLO
						elsif (funct = "010010") then
							ALU_op <= "011110";
						-- SLL
						elsif (funct = "000000") then
							ALU_OP <= "010010";
						-- SRL
						elsif (funct = "000010") then
							ALU_OP <= "010110";
						-- SRA
						elsif (funct = "000011") then
							ALU_OP <= "010100";
						-- NOP
						else
							ALU_op <= "000000";
						end if;
					-- I type
					elsif (opcode(5 downto 3) = "001") then
						
						ALU_src <= '1'; -- choose immediate
						reg_dst <= '0'; -- choose rt
			
						mem_read <= '0'; -- Currently none of the operation needs memeory access
						mem_write <= '0';
			
						mem_reg <= '0';
						reg_write_en <= '1';
			
						-- ORI
						if (opcode(2 downto 0) = "101") then
							ALU_op <= "000010";
						-- ADDI
						elsif (opcode(2 downto 0) = "000") then
							ALU_op <= "001001";
						-- SLTI
						elsif (opcode(2 downto 0) = "010") then
							ALU_op <= "001111";
						-- LUI
						elsif (opcode(2 downto 0) = "111") then
							ALU_op <= "000010";
						else
							invalid_inc := '1';
						end if;
					-- LW
					elsif (opcode = "100011") then
						ALU_src <= '1'; -- choose immediate
						ALU_op <= "001000"; -- always ADDU
						reg_dst <= '0'; -- choose rt

						mem_read <= '1'; -- memory read required
						mem_write <= '0';
						mem_reg <= '1';
						reg_write_en <= '1';
					-- SW
					elsif (opcode = "101011") then
						
						ALU_src <= '1'; -- choose immediate
						ALU_op <= "001000"; -- always ADDU
						reg_dst <= '0'; -- choose rt
						
						mem_read <= '0';
						mem_write <= '1'; -- memory write required
						mem_reg <= '0';
						reg_write_en <= '0';
						
					else
						invalid <= invalid_inc;
						ALU_op <= "000000";
						ALU_src <= '0'; 
						reg_dst <= '0'; 
				
						mem_read <= '0';
						mem_write <= '0';
				
						mem_reg <= '0';
						reg_write_en <= '0';
					end if;
				end if;
			end if;
		end if;
	end process;

end Behavioral;

